/* Copyright */
/*******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Tsunagi-BPM
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************************/
 /* Copyright End */
 
package tsunagi.rest;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONObject;

import tsunagi.common.Lang;
import tsunagi.common.SystemException;
import tsunagi.common.util.FileUtil;
import tsunagi.common.util.ObjectInstantiator;
import tsunagi.common.util.StringUtil;
import tsunagi.json.path.JsonPath;

public class RestLikeUtil {
	public static final String METHOD_GET = "get";
	public static final String METHOD_POST = "post";
	public static final String METHOD_PUT = "put";
	public static final String METHOD_DELET = "delete";
	
	private static RestLikeUtil INSTANCE = null;
	private static String configCache;
	private List<String> pathList = new ArrayList<String>();
	private Map<String, Object> analyzerclasses = new HashMap<String, Object>();
	private Map<String, Object> dataClasses = new HashMap<String, Object>();
	private Map<String, Object> procClasses = new HashMap<String, Object>();
	private JsonPath jp;
	private Map<String, RequestAnalyzer> requestAnalyzerCache = new HashMap<String, RequestAnalyzer>();
	private Map<String, IRequestProcessor> requestProcessorCache = new HashMap<String, IRequestProcessor>();
	
	public static RestLikeUtil getInstance(String config) {
		if (INSTANCE == null) {
			INSTANCE = new RestLikeUtil(config);
		}
		return INSTANCE;
	}
	
	public static RestLikeUtil getInstance() {
		if (INSTANCE == null) {
			throw new SystemException("RestLikeUtil is not initialied");
		}
 		return INSTANCE;
	}

	/**
	 * The config stores the directory name where resource files are defined.
	 * 
	 * @param config
	 */
	protected RestLikeUtil(String config) {
		configCache = config;
		String resources = this.buildAllResources(config);
		this.setJsonPath(new JsonPath(resources));
		this.initConfigData();
	}
	
	public void refresh() {
		analyzerclasses.clear();
		dataClasses.clear();
		procClasses.clear();
		requestAnalyzerCache.clear();
		requestProcessorCache.clear();
		String resources = this.buildAllResources(configCache);
		this.setJsonPath(new JsonPath(resources));
		this.initConfigData();
	}
	
	protected String buildAllResources(String config) {
		JsonPath jp = new JsonPath(config);
		String dir = (String)jp.get("/resource-dir");
		List<File> list = FileUtil.INSTANCE.listFile(dir);
		
		JSONObject all = new JSONObject();
		JSONArray array = new JSONArray();
		all.put("resources", array);
		
		try {
			for(File file: list) {
				FileInputStream fis = new FileInputStream(file);
				String content = StringUtil.INSTANCE.getContent(fis);
				fis.close();
				jp = new JsonPath(content);
				List<JSONObject> objs = (List<JSONObject>)jp.get("/resources");
				for(JSONObject obj: objs) {
					array.put(obj);
				}
			}
		}catch(Exception e) {
			new SystemException(e);
		}
		return all.toString();
	}
	
	protected void initConfigData() {
		this.setPathList((List<String>)this.getJsonPath().get("/resources/path"));
		this.initObjects("/resources/actions/requestAnalyzer", this.getAnalyzerclasses()); 
		this.initObjects("/resources/actions/requestProcessor/dataClass", this.getDataClasses()); 
		this.initObjects("/resources/actions/requestProcessor/procClass", this.getProcClasses()); 
	}
	
	protected void initObjects(String jsonPath, Map<String, Object>data) {
		List<String> list = (List<String>)this.getJsonPath().get(jsonPath);
		list = StringUtil.INSTANCE.removeDuplicate(list);
		for(String clazz : list) {
			Object obj = ObjectInstantiator.instantiate(clazz);
			data.put(clazz, obj);
		}
	}
	
	public RequestAnalyzer getRequestAnalyzer(String method, String url) {
		String resourcePath = this.getResourcePath(url);
		if (resourcePath == null) {
			return null;
		}
		String path = "/resources['path'='" + resourcePath + "']/actions['method'='" + method +"']";
		JSONObject obj = this.getJSONObjectOfThisResource(path);
		String clazz = obj.getString("requestAnalyzer");
		RequestAnalyzer analyzer  = requestAnalyzerCache.get(clazz);
		if (analyzer == null) {
			analyzer = (RequestAnalyzer)this.getAnalyzerclasses().get(clazz);
			requestAnalyzerCache.put(clazz, analyzer);
		}
		return analyzer;
	}
	
	public IRequestProcessor getRequestProcessor(String url, String method, String dataClass) {
		String resourcePath = this.getResourcePath(url);
		if (resourcePath == null) {
			return null;
		}
		String path = "/resources['path'='" + resourcePath + "']/actions['method'='" + method + "']/requestProcessor['dataClass'='" +  dataClass + "']";
		JSONObject obj = this.getJSONObjectOfThisResource(path);
		String clazz = obj.getString("procClass");
		IRequestProcessor processor = requestProcessorCache.get(clazz);
		if (processor == null) {
			processor = (IRequestProcessor)this.getProcClasses().get(clazz);
			requestProcessorCache.put(clazz, processor);
		}
		return processor;
	}

	protected JSONObject getJSONObjectOfThisResource(String path) {
		List<JSONObject> list = (List<JSONObject>)this.getJsonPath().get(path);
		return list.get(0);
	}
	
	public boolean isResourceDefined(String url) {
		boolean result = false;
		if (this.getResourcePath(url) != null) {
			result = true;
		}
		return result;
	}

	public String getResourcePath(String url) {
		String result = null;
		for(String path: this.getPathList()) {
			int index = url.indexOf(path);
			if (index < 0) {
				continue;
			}
			String temp = url.substring(1, index);
			if (!StringUtil.INSTANCE.isCharIncluded(Lang.STRING_SLASH, temp)) {
				temp = url.substring(index);
				if (path.equals(temp)) {
					result = path;
					break;
				}
			}
		}
		return result;
	}

	public JsonPath getJsonPath() {
		return jp;
	}
	
	public void setJsonPath(JsonPath val) {
		jp = val;
	}
	
	public Map<String, Object> getAnalyzerclasses() {
		return analyzerclasses;
	}

	public void setAnalyzerClasses(Map<String, Object> analyzerclasses) {
		this.analyzerclasses = analyzerclasses;
	}

	public Map<String, Object> getDataClasses() {
		return dataClasses;
	}

	public void setDataClasses(Map<String, Object> dataClasses) {
		this.dataClasses = dataClasses;
	}

	public Map<String, Object> getProcClasses() {
		return procClasses;
	}

	public void setProcClasses(Map<String, Object> procClasses) {
		this.procClasses = procClasses;
	}

	public List<String> getPathList() {
		return pathList;
	}
	
	public void setPathList(List<String> val) {
		pathList = val;
	}
}
